﻿using FilmBlowingMachine.Winform.Lib;
using MathNet.Numerics.LinearAlgebra;
using MathNet.Numerics.LinearAlgebra.Complex;
using System;
using System.Collections.Generic;

namespace FilmBlowingMachine.Winform.PLC
{
    public class DeltaAs200
    {
        public struct MeasuringPoint
        {
            public float Position { get; set; }
            public float Thickness { get; set; }
            public float Angle { get; set; }
            public float FilmLength { get; set; }
        }

        private readonly ModbusTcp _modbus;
        private ushort[] _heatingPwm;

        public DeltaAs200(string ip, int port, ushort numberOfHeatingPoints)
        {
            _modbus = new ModbusTcp(ip, port, 1);
            //NumberOfMeasuringPoints = numberOfMeasuringPoints;
            NumberOfHeatingPoints = numberOfHeatingPoints;
            _heatingPwm = new ushort[numberOfHeatingPoints];
            LengthAnglePairs = new SortedList<float, float>();
            ProcessedMeasuringPoints = new List<MeasuringPoint>();
            MaxPairs = 10000;
            MinFilmThickness = 1;
            MaxFilmThickness = 25;
            OffsetAngle = (float)165.54;
        }

        public bool IsConnected { get => _modbus.IsConnected; }

        /// <summary>
        /// 连接PLC
        /// </summary>
        /// <returns>连接成功时，返回true；连接失败时，返回false</returns>
        public bool Connect()
        {
            return _modbus.Connect();
        }

        public bool Disconnect()
        {
            return _modbus.Disconnect();
        }

        public void KeepAlive()
        {
            _modbus.KeepAlive();
        }

        public int NumberOfMeasuringPoints
        {
            get; set;
        }

        public ushort NumberOfHeatingPoints { get; set; }
        public MeasuringPoint[] MeasuringPoints { get; set; }
        public List<MeasuringPoint> ProcessedMeasuringPoints { get; set; }
        public float CylinderDiameter { get; set; }

        public float SlampingStartPosition
        {
            get => _modbus.ReadFloat(212).Value;
            set => _modbus.WriteFloat(212, value);
        }

        public float SlampingEndPosition
        {
            get => _modbus.ReadFloat(214).Value;
            set => _modbus.WriteFloat(214, value);
        }

        public ushort SlampingDelayTime
        {
            get => _modbus.ReadSingleWord(210).Value;
            set => _modbus.WriteSingleWord(210, value);
        }

        public int InitialHeatingNumber { get; set; }
        public float DistanceFromBlowingToMeasuring { get; set; }
        public SortedList<float, float> LengthAnglePairs { get; set; }
        private int MaxPairs { get; set; }

        public float FilmWidth
        {
            get => _modbus.ReadFloat(1002).Value;
            set => _modbus.WriteFloat(1002, value);
        }

        //public float ConveyorSpeed { get; set; }
        /// <summary>
        /// 控制字
        /// </summary>
        public ushort ControlWord
        {
            get => _modbus.ReadSingleWord(101).Value;
            set => _modbus.WriteSingleWord(101, value);
        }

        /// <summary>
        /// 状态字
        /// </summary>
        public ushort StatusWord { get; set; }

        /// <summary>
        /// 采样伺服当前位置
        /// </summary>
        public float MeasuringServoCurrentPosition => _modbus.ReadFloat(142).Value;

        public ushort[] HeatingPwm
        {
            get => _modbus.ReadHoldingRegisters(300, 120).Value;
            set
            {
                _heatingPwm = value;
                _modbus.WriteMultipleRegisters(300, _heatingPwm);
            }
        }

        /// <summary>
        /// 启动回零
        /// </summary>
        public bool Homing
        {
            get => _modbus.ReadSingleCoil(10).Value;
            set => _modbus.WriteSingleCoil(10, value);
        }

        /// <summary>
        /// 回零标志位
        /// </summary>
        public bool HomingFlag { get; set; }

        /// <summary>
        /// 回零完成标志
        /// </summary>
        public bool HomingFinished { get; set; }

        /// <summary>
        /// 回零快速频率
        /// </summary>
        public int HomingFastFreq
        {
            get => _modbus.ReadDWord(200).Value;
            set => _modbus.WriteDWord(200, value);
        }

        /// <summary>
        /// 回零慢速频率
        /// </summary>
        public int HomingLowFreq
        {
            get => _modbus.ReadDWord(202).Value;
            set => _modbus.WriteDWord(202, value);
        }

        /// <summary>
        /// 自动频率
        /// </summary>
        public int AutoFreq
        {
            get => _modbus.ReadDWord(1000).Value;
            set => _modbus.WriteDWord(1000, value);
        }

        public bool Running { get; set; }

        /// <summary>
        /// 每毫米对应脉冲数
        /// </summary>
        public float PulsePerMill
        {
            get => _modbus.ReadFloat(1004).Value;
            set => _modbus.WriteFloat(1004, value);
        }

        /// <summary>
        /// 当前膜长
        /// </summary>
        public float CurrentFilmLength { get => _modbus.ReadFloat(28).Value; }

        /// <summary>
        /// 当前角度
        /// </summary>
        public float CurrentAngle { get => _modbus.ReadFloat(30).Value; }

        //public float CurrentFilmThickness { get=>modbus.ReadSingleWord(600) }
        /// <summary>
        /// 第一次采样时膜长
        /// </summary>
        public float FirstSamplingFilmLength { get => (float)(_modbus.ReadDWord(220).Value / 1000.0 * 180.0); }

        /// <summary>
        /// 第二次采样时膜长
        /// </summary>
        public float SecondSamplingFilmLength { get => (float)(_modbus.ReadDWord(222).Value / 1000.0 * 180.0); }

        /// <summary>
        /// 加热点间隔
        /// </summary>
        public float PointsSpacing { get => FilmWidth * 2 / NumberOfHeatingPoints; }

        /// <summary>
        /// 第二次采样时，起始索引值
        /// </summary>
        public ushort SecondSamplingStartIndex { get => _modbus.ReadSingleWord(222).Value; }

        /// <summary>
        /// 最小膜厚
        /// </summary>
        public float MinFilmThickness { get; set; }

        /// <summary>
        /// 最小膜厚
        /// </summary>
        public float MaxFilmThickness { get; set; }

        public float OffsetAngle { get; set; }

        public bool JogP
        {
            get => _modbus.ReadSingleCoil(120).Value;
            set => _modbus.WriteSingleCoil(120, value);
        }

        public bool JogN
        {
            get => _modbus.ReadSingleCoil(121).Value;
            set => _modbus.WriteSingleCoil(121, value);
        }


        /// <summary>
        /// 获取测量数据
        /// </summary>
        /// <returns>获取数据成功时，返回true；否则返回false</returns>
        public bool GetMeasuringPoints()
        {
            int regNum = 7;
            NumberOfMeasuringPoints = (_modbus.ReadDWord(0).Value / regNum) + 1;
            MeasuringPoints = new MeasuringPoint[NumberOfMeasuringPoints];
            ushort[] buf = _modbus.ReadHoldingRegisters(4000, (NumberOfMeasuringPoints * regNum)).Value;

            if (buf == null) return false;
            for (int i = 0; i < NumberOfMeasuringPoints; i++)
            {
                int offset = i * regNum;
                MeasuringPoints[i].Thickness = ADValue2FilmThickness(buf[offset]);
                MeasuringPoints[i].Angle = BitConverter.ToSingle(BitConverter.GetBytes(buf[offset + 1] | (uint)(buf[offset + 2] << 16)), 0);
                MeasuringPoints[i].Position = BitConverter.ToSingle(BitConverter.GetBytes(buf[offset + 3] | (uint)(buf[offset + 4] << 16)), 0);
                MeasuringPoints[i].FilmLength = BitConverter.ToSingle(BitConverter.GetBytes(buf[offset + 5] | (uint)(buf[offset + 6] << 16)), 0);
            }
            return true;
        }

        /// <summary>
        /// 过滤从PLC获取的数据，转换成计算时所需要的数据
        /// </summary>
        /// <returns>构建矩阵所需要的数据</returns>
        private List<MeasuringPoint> FilterData()
        {
            foreach (var point in MeasuringPoints)
            {
                if (EvalFilmThickness(point.Thickness))
                {
                    ProcessedMeasuringPoints.Add(point);
                }
            }
            return ProcessedMeasuringPoints;
        }

        /// <summary>
        /// 求解矩阵，返回120个加热点对应的厚度
        /// </summary>
        /// <returns></returns>
        public float[] ComputeFilmThickness()
        {
            float[] ans = new float[NumberOfMeasuringPoints];

            return ans;
        }

        //int cnt;
        public void RecordLengthAnglePairs()
        {
            //CurremtFilmLength = cnt++;
            //CurrentAngle = cnt++;
            AddLengthAnglePairs(CurrentFilmLength, CurrentAngle);
        }

        /// <summary>
        /// 将键值对添加到队列中
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        private void AddLengthAnglePairs(float key, float value)
        {
            if (LengthAnglePairs.ContainsKey(key))
            {
                LengthAnglePairs[key] = value;
                return;
            }
            if (LengthAnglePairs.Count >= MaxPairs)
            {
                LengthAnglePairs.RemoveAt(0);
            }

            LengthAnglePairs.Add(key, value);
        }

        /// <summary>
        /// 从有序队列中选择两个角度
        /// </summary>
        /// <returns></returns>
        private float[] SelectAngleFromPairs()
        {
            float[] res = new float[2];
            bool[] resFound = new bool[2];
            float[] lens = new float[] { FirstSamplingFilmLength, SecondSamplingFilmLength };

            for (int index = 0; index < LengthAnglePairs.Count - 2; index++)
            {
                if (LengthAnglePairs[index] >= lens[0] - DistanceFromBlowingToMeasuring && !resFound[0])
                {
                    resFound[0] = true;
                    res[0] = LengthAnglePairs[index];
                }

                if (LengthAnglePairs[index] >= lens[1] - DistanceFromBlowingToMeasuring && !resFound[1])
                {
                    resFound[1] = true;
                    res[1] = LengthAnglePairs[index];
                }
            }
            return res;
        }

        /// <summary>
        /// 采样计算，并输出到plc
        /// </summary>
        /// <param name="interactionEnabled">交互式使能</param>
        /// <param name="outputEnabled">输出使能</param>
        public void StartSampling(bool interactionEnabled, bool outputEnabled)
        {
            if (interactionEnabled)
            {
                if (StatusWord == 1 && ControlWord == 0)
                {
                    //float[] angles = SelectAngleFromPairs();
                    //GetMeasuringPoints();

                    ControlWord = 1;
                }
                else if (StatusWord == 0 && ControlWord == 1)
                {
                    ControlWord = 0;
                }
            }
        }

        /// <summary>
        /// 将16位AD数值转换成膜厚
        /// </summary>
        /// <param name="adValue">16bits AD</param>
        /// <returns>膜厚 单位：微米</returns>
        public float ADValue2FilmThickness(ushort adValue)
        {
            return (float)((10643 - adValue) / 333.45);
        }

        private bool EvalFilmThickness(float filmThickness)
        {
            if (filmThickness > MinFilmThickness && filmThickness < MaxFilmThickness)
            {
                return true;
            }
            else
            {
                return false;
            }
        }

    }
}